<?php
/**
 * 官方文档：http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
 * 微信支付：http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=9_1#
 * 官方示例：http://demo.open.weixin.qq.com/jssdk/sample.zip
 * UCToo示例:http://git.oschina.net/uctoo/uctoo/blob/master/Addons/Jssdk/Controller/JssdkController.class.php
 * 
 * 微信JSSDK支付类,主要实现了微信JSSDK支付，参考官方提供的示例文档，
 * @命名空间版本
 * @author uctoo (www.uctoo.com)
 * @date 2015-5-15 14:10
 */
namespace wechat;

class Jspay {
  private $appId;
  private $appSecret;
  public $debug = false;
  public $weObj;      //微信类实例
  public $parameters;//获取prepay_id时的请求参数
  //受理商ID，身份标识
  public $MCHID = '';
  //商户支付密钥Key。审核通过后，在微信商户平台中查看 https://pay.weixin.qq.com
  public $KEY = '';
  //=======【JSAPI路径设置】===================================
  //获取access_token过程中的跳转uri，通过跳转将code传入jsapi支付页面
  public $JS_API_CALL_URL = '';
  //=======【证书路径设置】=====================================
  //证书路径,注意应该填写绝对路径
 // protected \$SSLCERT_PATH = 'cert/apiclient_cert.pem';//证书路径
 //  protected \$SSLKEY_PATH = 'cert/apiclient_key.pem';//证书路径
 //  protected \$opUserId = '1234567899';//商户号

  public $SSLCERT_PATH = '/xxx/xxx/xxxx/WxPayPubHelper/cacert/apiclient_cert.pem';
  public $SSLKEY_PATH = '/xxx/xxx/xxxx/WxPayPubHelper/cacert/apiclient_key.pem';
  //=======【异步通知url设置】===================================
  //异步通知url，商户根据实际开发过程设定
  //C('url')."admin.php/order/notify_url.html";
  public $NOTIFY_URL = '';
  //=======【curl超时设置】===========`========================
  //本例程通过curl使用HTTP POST方法，此处可修改其超时时间，默认为30秒
  public  $curl_timeout = 30;
  public  $prepay_id;
  public function __construct($options) {
    $this->parameters = $options;
    $this->appId = $options['appid'];
    $this->appSecret = $options['appsecret'];
    $this->MCHID = $options['mchid'];
    $this->KEY = $options['key'];
    $this->SSLCERT_PATH = $options['apiclient_cert'];
    $this->SSLKEY_PATH = $options['apiclient_key'];
    $this->weObj = new Wxextends(['appid'=>$this->appId,'appsecret'=>$this->appSecret]);
  }
  //微信支付相关方法
  /**
   * 	作用：格式化参数，签名过程需要使用
   */
  function formatBizQueryParaMap($paraMap, $urlencode)
  {
    $buff = "";
    ksort($paraMap);
    foreach ($paraMap as $k => $v)
    {
      if($urlencode)
      {
        $v = urlencode($v);
      }
      //$buff .= strtolower($k) . "=" . $v . "&";
      $buff .= $k . "=" . $v . "&";
    }
    $reqPar = "";
    if (strlen($buff) > 0)
    {
      $reqPar = substr($buff, 0, strlen($buff)-1);
    }
    return $reqPar;
  }
  /**
   * 	作用：设置jsapi的参数
   */
  public function getParameters()
  {
    $jsApiObj["appId"] = $this->appId;           //请求生成支付签名时需要,js调起支付参数中不需要
    $timeStamp = time();
    $jsApiObj["timeStamp"] = "$timeStamp";      //用大写的timeStamp参数请求生成支付签名
    $jsParamObj["timestamp"] = "$timeStamp";      //用小写的timestamp参数生成js支付参数，还要注意数据类型，坑！
    $jsParamObj["nonceStr"] = $jsApiObj["nonceStr"] = $this->weObj->generateNonceStr();
    $jsParamObj["package"] = $jsApiObj["package"] = "prepay_id=".$this->getPrepayId();
    $jsParamObj["signType"] = $jsApiObj["signType"] = "MD5";
    $jsParamObj["paySign"] = $jsApiObj["paySign"] = $this->getSign($jsApiObj);
    // $jsParam = json_encode($jsParamObj);
    return $jsParamObj;
  }

  /**
   *  作用：设置退款jsapi的参数
   */
  public function wxrefundapi(){
    //echo $this->SSLCERT_PATH;die;
    $postXml = $this->createRefundXml();
    $xmlresult = $this->postXmlSSLCurl($postXml,'https://api.mch.weixin.qq.com/secapi/pay/refund',true);
    $array_data = json_decode(json_encode(simplexml_load_string($xmlresult, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    return $array_data;
}
  /**
   * 获取prepay_id
   */
  function getPrepayId()
  {
    $result = $this->xmlToArray($this->postXml());
    // print_r($result);die;
    if (!array_key_exists("appid", $result)
        || !array_key_exists("prepay_id", $result)
        || $result['prepay_id'] == ""
    ) {
        throw new \Exception("参数错误");
    }
    $prepay_id = $result["prepay_id"];
    return $prepay_id;
  }
  /**
   * 	作用：将xml转为array
   */
  public function xmlToArray($xml)
  {
    //将XML转为array
    $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    return $array_data;
  }
  /**
   * 	作用：post请求xml
   */
  function postXml()
  {
    $xml = $this->createXml();
    return  $this->postXmlCurl($xml,"https://api.mch.weixin.qq.com/pay/unifiedorder",$this->curl_timeout);
  }

  public function postXmlSSLCurl($xml, $url, $useCert = false, $second = 30)
  {   
    $ch = curl_init();
    $curlVersion = curl_version();
    $ua = "WXPaySDK/3.0.9 (".PHP_OS.") PHP/".PHP_VERSION." CURL/".$curlVersion['version']." "
    .$this->parameters['mchid'];

    //设置超时
    curl_setopt($ch, CURLOPT_TIMEOUT, $second);

    $proxyHost = "0.0.0.0";
    $proxyPort = 0;
    //如果有配置代理这里就设置代理
    if($proxyHost != "0.0.0.0" && $proxyPort != 0){
      curl_setopt($ch,CURLOPT_PROXY, $proxyHost);
      curl_setopt($ch,CURLOPT_PROXYPORT, $proxyPort);
    }
    curl_setopt($ch,CURLOPT_URL, $url);
    curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
    curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
    curl_setopt($ch,CURLOPT_USERAGENT, $ua); 
    //设置header
    curl_setopt($ch, CURLOPT_HEADER, FALSE);
    //要求结果为字符串且输出到屏幕上
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  
    if($useCert == true){
      //设置证书
      //使用证书：cert 与 key 分别属于两个.pem文件
      //证书文件请放入服务器的非web目录下
      curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
      curl_setopt($ch,CURLOPT_SSLCERT, $this->SSLCERT_PATH);
      curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
      curl_setopt($ch,CURLOPT_SSLKEY, $this->SSLKEY_PATH);
    }
    //post提交方式
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
    //运行curl
    $data = curl_exec($ch);
    //返回结果
    if($data){
      curl_close($ch);
      return $data;
    } else { 
      $error = curl_errno($ch);
      curl_close($ch);
      echo "curl出错，错误码:$error";die;
      throw new WxPayException("curl出错，错误码:$error");
    }
  }

  /**
   * 	作用：以post方式提交xml到对应的接口url
   */
  public function postXmlCurl($xml,$url,$second=30)
  {
    //初始化curl
    $ch = curl_init();
    //设置超时
    curl_setopt($ch,CURLOPT_TIMEOUT, $second);
    //这里设置代理，如果有的话
    //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
    //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
    curl_setopt($ch,CURLOPT_URL, $url);
    curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
    curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
    //设置header
    curl_setopt($ch, CURLOPT_HEADER, FALSE);
    //要求结果为字符串且输出到屏幕上
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    //post提交方式
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
    //运行curl
    $data = curl_exec($ch);
    // curl_close($ch);
    //返回结果
    if($data)
    {
      curl_close($ch);
      return $data;
    }
    else
    {
      $error = curl_errno($ch);
      echo "curl出错，错误码:$error"."<br>";
      echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
      curl_close($ch);
      return false;
    }
  }
  /**
   * 	作用：设置标配的请求参数，生成签名，生成接口参数xml
   */
  function createXml()
  {
    $this->parametersArr["appid"] = $this->appId;//公众账号ID
    $this->parametersArr["mch_id"] = $this->MCHID;//商户号
    $this->parametersArr["nonce_str"] = $this->weObj->generateNonceStr();//随机字符串
    $this->parametersArr["body"] = $this->parameters['body'];
    $this->parametersArr["out_trade_no"] = $this->parameters['out_trade_no'];
    $this->parametersArr["openid"] = $this->parameters['openid'];
    $this->parametersArr["total_fee"] = $this->parameters['total_fee'];
    $this->parametersArr["notify_url"] = $this->parameters['notify_url']; // 终端ip
    $this->parametersArr["spbill_create_ip"] = $this->parameters['spbill_create_ip']; // 终端ip
    $this->parametersArr["trade_type"] = 'JSAPI'; 
    $this->parametersArr["sign"] = $this->getSign($this->parametersArr);//签名
    return  $this->arrayToXml($this->parametersArr);
  }
  
  /**
   *  作用：设置标配的请求参数，生成签名，生成接口参数xml（退款）
   */
  function createRefundXml()
  {
      $str = $this->weObj->generateNonceStr();
      $jsApiObj["appid"] = $this->appId;           //请求生成支付签名时需要,js调起支付参数中不需要
      $jsApiObj["mch_id"] = $this->MCHID;  
      $jsApiObj["nonce_str"] = $str;
      $jsApiObj["out_refund_no"] = $this->parameters['out_refund_no'];  
      $jsApiObj["out_trade_no"] = $this->parameters['out_trade_no'];  
      $jsApiObj["total_fee"] = $this->parameters['total_fee'];  
      $jsApiObj["refund_fee"] = $this->parameters['total_fee'];  

      $this->parametersArr["appid"] = $this->appId;//公众账号ID
      $this->parametersArr["mch_id"] = $this->MCHID;//商户号
      $this->parametersArr["nonce_str"] = $str;//随机字符串
      $this->parametersArr["out_refund_no"] = $this->parameters['out_refund_no'];
      $this->parametersArr["out_trade_no"] = $this->parameters['out_trade_no'];
      $this->parametersArr["total_fee"] = $this->parameters['total_fee'];
      $this->parametersArr["refund_fee"] = $this->parameters['total_fee'];
      $this->parametersArr["sign"] = $this->getRefundSign($jsApiObj);
      return  $this->arrayToXml($this->parametersArr);
  }
   /**
   * 	作用：array转xml
   */
  function arrayToXml($arr)
  {
    $xml = "<xml>";
    foreach ($arr as $key=>$val)
    {
      if (is_numeric($val))
      {
        $xml.="<".$key.">".$val."</".$key.">";
      }
      else
        $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
    }
    $xml.="</xml>";
    return $xml;
  }
  /**
   *  作用：生成退款签名
   */
  public function getRefundSign($Obj)
  {
    foreach ($Obj as $k => $v)
    {
      $Parameters[$k] = $v;
    }
    //签名步骤一：按字典序排序参数
    ksort($Parameters);
    $string = $this->ToUrlParams($Parameters);
    //签名步骤二：在string后加入KEY
    $string = $string . "&key=".$this->KEY;
    //签名步骤三：MD5加密或者HMAC-SHA256
    $string = md5($string);
    
    //签名步骤四：所有字符转为大写
    $result = strtoupper($string);
    return $result;
  }

  public function ToUrlParams($obj)
  {
    $buff = "";
    foreach ($obj as $k => $v)
    {
      if($k != "sign" && $v != "" && !is_array($v)){
        $buff .= $k . "=" . $v . "&";
      }
    }
    
    $buff = trim($buff, "&");
    return $buff;
  }
  /**
   *  作用：生成签名
   */
  public function getSign($Obj)
  {
    foreach ($Obj as $k => $v)
    {
      $Parameters[$k] = $v;
    }
    //签名步骤一：按字典序排序参数
    ksort($Parameters);
    $String = $this->formatBizQueryParaMap($Parameters, false);
    //echo '【string1】'.$String.'</br>';
    //签名步骤二：在string后加入KEY
    $String = $String."&key=".$this->KEY;
    //echo "【string2】".$String."</br>";
    //签名步骤三：MD5加密
    $String = md5($String);
    //echo "【string3】 ".$String."</br>";
    //签名步骤四：所有字符转为大写
    $result_ = strtoupper($String);
    //echo "【result】 ".$result_."</br>";
    return $result_;
  }
	
}